﻿// MvcRouteUnitTester
// by Lee Dumond 
// twitter.com/LeeDumond

namespace MvcRouteUnitTester.Tests
{
   using System.Web;
   using System.Web.Mvc;
   using System.Web.Routing;
   using NUnit.Framework;
   using AssertionException = MvcRouteUnitTester.AssertionException;

   [TestFixture]
   public class RequestInfoTests
   {
      [Test]
      public void CanAlterContext()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "Home/About",
            new {controller = "Home", action = "About"}
            );

         var tester = new RouteTester(routeCollection);
         RequestInfo requestInfo = tester.WithIncomingRequest("/Home/About");
         HttpContextBase context = requestInfo.HttpContext;
         context.Items.Add("key", "value");

         Assert.DoesNotThrow(() => { requestInfo.ShouldMatchRoute("Home", "About"); },
                             "Requested URL should have matched a route.");
         Assert.AreEqual("value", requestInfo.HttpContext.Items["key"]);
      }

      [Test]
      public void ShouldBeIgnored_IsIgnored_ShouldWorkFine()
      {
         var routeCollection = new RouteCollection();
         routeCollection.IgnoreRoute(
            "Home/Ignored"
            );

         var tester = new RouteTester(routeCollection);

         Assert.DoesNotThrow(() => { tester.WithIncomingRequest("/Home/Ignored").ShouldBeIgnored(); },
                             "Requested URL should have been ignored.");
      }

      [Test]
      public void ShouldBeIgnored_UrlNotIgnored_ShouldThrowAssertionException()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "Home/About",
            new {controller = "Home", action = "About"}
            );

         var tester = new RouteTester(routeCollection);

         var ex =
            Assert.Throws<AssertionException>(
               () => { tester.WithIncomingRequest("/Home/About").ShouldBeIgnored(); },
               "Request that is not ignored should throw an AssertionException.");

         Assert.AreEqual("The request was not ignored (for url: \"/Home/About\").", ex.Message);
      }

      [Test]
      public void ShouldMatchNoRoute_NoMatch_ShouldWorkFine()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "Home/About",
            new {controller = "Home", action = "About"}
            );

         var tester = new RouteTester(routeCollection);

         Assert.DoesNotThrow(() => { tester.WithIncomingRequest("/Home/ForceNoMatch").ShouldMatchNoRoute(); },
                             "Requested URL should not have matched a route.");
      }

      [Test]
      public void ShouldMatchNoRoute_UrlIncorrectlyMatches_ShouldThrowAssertionException()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "Home/About",
            new {controller = "Home", action = "About"}
            );

         var tester = new RouteTester(routeCollection);

         var ex =
            Assert.Throws<AssertionException>(
               () => { tester.WithIncomingRequest("/Home/About").ShouldMatchNoRoute(); },
               "Request that matches a route should throw an AssertionException.");

         Assert.AreEqual("A matching route was found (for url: \"/Home/About\").", ex.Message);
      }

      [Test]
      public void ShouldMatchRoute_ActionMismatch_ShouldThrowAssertionException()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "Home/About",
            new {controller = "Home", action = "About"}
            );

         var tester = new RouteTester(routeCollection);

         var ex =
            Assert.Throws<AssertionException>(
               () => { tester.WithIncomingRequest("/Home/About").ShouldMatchRoute("Home", "IntentionallyNotRight"); },
               "A non-matching action should throw an AssertionException.");

         Assert.AreEqual(
            "Action name mismatch. Expected: \"IntentionallyNotRight\", but was: \"About\" (for url: \"/Home/About\").",
            ex.Message);
      }

      [Test]
      public void ShouldMatchRoute_ActualRouteValuesNull_ShouldThrowAssertionException()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "Home/About",
            new {controller = "Home", action = "About", routevaluex = "x", routevaluey = "y"}
            );

         var tester = new RouteTester(routeCollection);

         var ex =
            Assert.Throws<AssertionException>(
               () => { tester.WithIncomingRequest("/Home/About").ShouldMatchRoute("Home", "About"); },
               "Not supplying default route values should throw an AssertionException.");

         Assert.AreEqual(
            "Route values mismatch. Expected: 0 route values, but was: 2 route values (for url: \"/Home/About\").",
            ex.Message);
      }

      [Test]
      public void ShouldMatchRoute_AreaMatch_ShouldWorkFine()
      {
         var routeCollection = new RouteCollection();
         var route = new Route("Admin/{controller}/{action}", null, null, new RouteValueDictionary(new {area = "Admin"}),
                               new MvcRouteHandler());
         routeCollection.Add(route);

         var tester = new RouteTester(routeCollection);

         Assert.DoesNotThrow(
            () => { tester.WithIncomingRequest("/Admin/Home/About").ShouldMatchRoute("Admin", "Home", "About"); },
            "Requested URL should have matched a route.");
      }

      [Test]
      public void ShouldMatchRoute_AreaMismatch_ShouldThrowAssertionException()
      {
         var routeCollection = new RouteCollection();
         var route = new Route("Admin/{controller}/{action}", null, null, new RouteValueDictionary(new {area = "Admin"}),
                               new MvcRouteHandler());
         routeCollection.Add(route);

         var tester = new RouteTester(routeCollection);

         var ex =
            Assert.Throws<AssertionException>(
               () => { tester.WithIncomingRequest("/Admin/Home/About").ShouldMatchRoute("Catalog", "Home", "About"); },
               "A non-matching area should throw an AssertionException.");

         Assert.AreEqual(
            "Area name mismatch. Expected: \"Catalog\", but was: \"Admin\" (for url: \"/Admin/Home/About\").",
            ex.Message);
      }

      [Test]
      public void ShouldMatchRoute_ControllerMismatch_ShouldThrowAssertionException()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "Home/About",
            new {controller = "Home", action = "About"}
            );

         var tester = new RouteTester(routeCollection);

         var ex =
            Assert.Throws<AssertionException>(
               () => { tester.WithIncomingRequest("/Home/About").ShouldMatchRoute("IntentionallyNotRight", "About"); },
               "A non-matching controller should throw an AssertionException.");

         Assert.AreEqual(
            "Controller name mismatch. Expected: \"IntentionallyNotRight\", but was: \"Home\" (for url: \"/Home/About\").",
            ex.Message);
      }

      [Test]
      public void ShouldMatchRoute_CustomConstraintMatch_ShouldWorkFine()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "{controller}/{action}/{id}",
            null,
            new {constraint = new TestCustomConstraint()}
            );

         var tester = new RouteTester(routeCollection);

         Assert.DoesNotThrow(
            () => { tester.WithIncomingRequest("/Home/About/ABC5").ShouldMatchRoute("Home", "About", new {id = "ABC5"}); },
            "Requested URL should have matched a route.");
      }

      [Test]
      public void ShouldMatchRoute_CustomConstraintMismatch_ShouldThrowAssertionException()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "{controller}/{action}/{id}",
            null,
            new {constraint = new TestCustomConstraint()}
            );

         var tester = new RouteTester(routeCollection);

         var ex =
            Assert.Throws<AssertionException>(
               () => { tester.WithIncomingRequest("/Home/About/5").ShouldMatchRoute("Home", "About", new {id = 5}); },
               "Request that violates custom constraint should throw an AssertionException.");

         Assert.AreEqual("No matching route was found (for url: \"/Home/About/5\").", ex.Message);
      }

      [Test]
      public void ShouldMatchRoute_ExpectedRouteValueMissing_ShouldThrowAssertionException()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "Home/About",
            new {controller = "Home", action = "About", routevaluex = "x", routevaluey = "y"}
            );

         var tester = new RouteTester(routeCollection);

         var ex =
            Assert.Throws<AssertionException>(
               () => { tester.WithIncomingRequest("/Home/About").ShouldMatchRoute("Home", "About", new {routevaluex = "x"}); },
               "Not supplying default route values should throw an AssertionException.");

         Assert.AreEqual(
            "Route values mismatch. Unexpected route value with key \"routevaluey\" and value \"y\" was found (for url: \"/Home/About\").",
            ex.Message);
      }

      [Test]
      public void ShouldMatchRoute_Match_ShouldWorkFine()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "Home/About",
            new {controller = "Home", action = "About"}
            );

         var tester = new RouteTester(routeCollection);

         Assert.DoesNotThrow(() => { tester.WithIncomingRequest("/Home/About").ShouldMatchRoute("Home", "About"); },
                             "Requested URL should have matched a route.");
      }

      [Test]
      public void ShouldMatchRoute_MatchedRouteHasNullRouteValues_ShouldThrowAssertionException()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "Home/About",
            new {controller = "Home", action = "About"}
            );

         var tester = new RouteTester(routeCollection);

         var ex =
            Assert.Throws<AssertionException>(
               () =>
                  {
                     tester.WithIncomingRequest("/Home/About").ShouldMatchRoute("Home", "About",
                                                                                new {routevaluex = "x"});
                  },
               "Supplying non-existent route values should throw an AssertionException.");

         Assert.AreEqual(
            "Route values mismatch. Expected: 1 route values, but was: 0 route values (for url: \"/Home/About\").",
            ex.Message);
      }

      [Test]
      public void ShouldMatchRoute_MatchedRouteValueMismatch_ShouldThrowAssertionException()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "Home/About",
            new {controller = "Home", action = "About", routevaluex = "x"}
            );

         var tester = new RouteTester(routeCollection);

         var ex =
            Assert.Throws<AssertionException>(
               () =>
                  {
                     tester.WithIncomingRequest("/Home/About").ShouldMatchRoute("Home", "About",
                                                                                new {routevaluex = "notx"});
                  },
               "Supplying an incorrect route value should throw an AssertionException.");

         Assert.AreEqual(
            "Route values mismatch. Expected: route value with key \"routevaluex\" and value \"notx\", but was: route value with key \"routevaluex\" and value \"x\" (for url: \"/Home/About\").",
            ex.Message);
      }

      [Test]
      public void ShouldMatchRoute_MatchedRouteValueMissing_ShouldThrowAssertionException()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "Home/About",
            new {controller = "Home", action = "About", routevaluex = "x"}
            );

         var tester = new RouteTester(routeCollection);

         var ex =
            Assert.Throws<AssertionException>(
               () =>
                  {
                     tester.WithIncomingRequest("/Home/About").ShouldMatchRoute("Home", "About",
                                                                                new
                                                                                   {
                                                                                      routevaluex = "x",
                                                                                      routevaluey = "y"
                                                                                   });
                  },
               "Supplying a non-existent route value should throw an AssertionException.");

         Assert.AreEqual(
            "Route values mismatch. Expected route value with key \"routevaluey\" was not found (for url: \"/Home/About\").",
            ex.Message);
      }

      [Test]
      public void ShouldMatchRoute_MethodConstraintMatch_ShouldWorkFine()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "{controller}/{action}",
            null,
            new {httpMethod = new HttpMethodConstraint(new[] {"POST"})}
            );

         var tester = new RouteTester(routeCollection);


         Assert.DoesNotThrow(
            () => { tester.WithIncomingRequest("/Home/About", "POST").ShouldMatchRoute("Home", "About"); },
            "Requested URL should have matched a route.");
      }

      [Test]
      public void ShouldMatchRoute_MethodConstraintMismatch_ShouldThrowAssertionException()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "{controller}/{action}",
            null,
            new {httpMethod = new HttpMethodConstraint(new[] {"POST"})}
            );

         var tester = new RouteTester(routeCollection);

         var ex =
            Assert.Throws<AssertionException>(
               () => { tester.WithIncomingRequest("/Home/About").ShouldMatchRoute("Home", "About"); },
               "Request that violates method constraint should throw an AssertionException.");

         Assert.AreEqual("No matching route was found (for url: \"/Home/About\").", ex.Message);
      }

      [Test]
      public void ShouldMatchRoute_RegexConstraintMatch_ShouldWorkFine()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "{controller}/{action}/{id}",
            null,
            new {id = @"\d+"}
            );

         var tester = new RouteTester(routeCollection);

         Assert.DoesNotThrow(
            () => { tester.WithIncomingRequest("/Home/About/1234").ShouldMatchRoute("Home", "About", new {id = 1234}); },
            "Requested URL should have matched a route.");
      }

      [Test]
      public void ShouldMatchRoute_RegexConstraintMismatch_ShouldThrowAssertionException()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "{controller}/{action}/{id}",
            null,
            new {id = @"\d+"}
            );

         var tester = new RouteTester(routeCollection);

         var ex =
            Assert.Throws<AssertionException>(
               () =>
                  {
                     tester.WithIncomingRequest("/Home/About/someId").ShouldMatchRoute("Home", "About",
                                                                                       new {id = "someId"});
                  },
               "Request that violates regex mismatch should throw an AssertionException.");

         Assert.AreEqual("No matching route was found (for url: \"/Home/About/someId\").", ex.Message);
      }

      [Test]
      public void ShouldMatchRoute_RouteNotFound_ShouldThrowAssertionException()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "Home/About",
            new {controller = "Home", action = "About", routevaluex = "x", routevaluey = "y"}
            );

         var tester = new RouteTester(routeCollection);

         var ex =
            Assert.Throws<AssertionException>(
               () => { tester.WithIncomingRequest("/Home/ForceFail").ShouldMatchRoute("Home", "ForceFail"); },
               "Request that does not match any route should throw an AssertionException.");

         Assert.AreEqual("No matching route was found (for url: \"/Home/ForceFail\").", ex.Message);
      }

      [Test]
      public void ShouldMatchRoute_UsingAreaOverloadWithNoArea_ShouldThrowAssertionException()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "{controller}/{action}"
            );

         var tester = new RouteTester(routeCollection);

         var ex =
            Assert.Throws<AssertionException>(
               () => { tester.WithIncomingRequest("/Home/About").ShouldMatchRoute("Admin", "Home", "About"); },
               "Using the area overload for a non-area route test should throw an AssertionException.");

         Assert.AreEqual("Area name mismatch. Expected: \"Admin\", but was: \"\" (for url: \"/Home/About\").",
                         ex.Message);
      }

      [Test]
      public void ShouldMatchRoute_UsingNoAreaOverloadWithArea_ShouldThrowAssertionException()
      {
         var routeCollection = new RouteCollection();
         var route = new Route("Admin/{controller}/{action}", null, null, new RouteValueDictionary(new {area = "Admin"}),
                               new MvcRouteHandler());
         routeCollection.Add(route);

         var tester = new RouteTester(routeCollection);

         var ex =
            Assert.Throws<AssertionException>(
               () => { tester.WithIncomingRequest("/Admin/Home/About").ShouldMatchRoute("Home", "About"); },
               "Using the non-area overload for an area route test should throw an AssertionException.");

         Assert.AreEqual("Area name mismatch. Expected: \"\", but was: \"Admin\" (for url: \"/Admin/Home/About\").",
                         ex.Message);
      }

      [Test]
      public void ShouldMatchRoute_WithDefaultNonStringValue_ShouldWorkFine()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "{controller}/{action}/{id}",
            new {controller = "Home", action = "About", id = 5}
            );

         var tester = new RouteTester(routeCollection);

         Assert.DoesNotThrow(
            () =>
               {
                  tester.WithIncomingRequest("/Home/About").ShouldMatchRoute("Home", "About",
                                                                             new
                                                                                {
                                                                                   id = 5,
                                                                                });
               },
            "Requested URL should have matched a route.");
      }

      [Test]
      public void ShouldMatchRoute_WithDefaultNullValueExpectedNonNullValue_ShouldThrowAssertionException()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "{controller}/{action}/{id}",
            new {controller = "Home", action = "About", id = (int?) null}
            );

         var tester = new RouteTester(routeCollection);

         var ex =
            Assert.Throws<AssertionException>(
               () => { tester.WithIncomingRequest("/Home/About").ShouldMatchRoute("Home", "About", new {id = 5}); },
               "Supplying incorrect default route value should throw an AssertionException.");

         Assert.AreEqual(
            "Route values mismatch. Expected: route value with key \"id\" and value \"5\", but was: route value with key \"id\" and value \"\" (for url: \"/Home/About\").",
            ex.Message);
      }

      [Test]
      public void ShouldMatchRoute_WithDefaultNullValue_ShouldWorkFine()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "Home/About",
            new {controller = "Home", action = "About", default1 = (string) null}
            );

         var tester = new RouteTester(routeCollection);

         Assert.DoesNotThrow(
            () =>
               {
                  tester.WithIncomingRequest("/Home/About").ShouldMatchRoute("Home", "About",
                                                                             new
                                                                                {
                                                                                   default1 = (string) null,
                                                                                });
               },
            "Requested URL should have matched a route.");
      }

      [Test]
      public void ShouldMatchRoute_WithDefaultNullableValue_ShouldWorkFine()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "Home/About",
            new {controller = "Home", action = "About", default1 = (int?) null}
            );

         var tester = new RouteTester(routeCollection);

         Assert.DoesNotThrow(
            () =>
               {
                  tester.WithIncomingRequest("/Home/About").ShouldMatchRoute("Home", "About",
                                                                             new
                                                                                {
                                                                                   default1 = (int?) null,
                                                                                });
               },
            "Requested URL should have matched a route.");
      }

      [Test]
      public void ShouldMatchRoute_WithDefaultStringValue_ShouldWorkFine()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "Home/About",
            new {controller = "Home", action = "About", default1 = "defaultValue1"}
            );

         var tester = new RouteTester(routeCollection);

         Assert.DoesNotThrow(
            () =>
               {
                  tester.WithIncomingRequest("/Home/About").ShouldMatchRoute("Home", "About",
                                                                             new {default1 = "defaultValue1"});
               },
            "Requested URL should have matched a route.");
      }

      [Test]
      public void ShouldMatchRoute_WithOptionalParameter_ShouldWorkFine()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "Home/About/{id}",
            new {controller = "Home", action = "About", id = UrlParameter.Optional}
            );

         var tester = new RouteTester(routeCollection);

         Assert.DoesNotThrow(
            () => { tester.WithIncomingRequest("/Home/About").ShouldMatchRoute("Home", "About"); },
            "Requested URL should have matched a route.");
      }

      [Test]
      public void ShouldMatchRoute_WithRequestAndDefaultParameters_ShouldWorkFine()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "{controller}/{action}/{id}/{slug}",
            new {slug = "default-slug"}
            );

         var tester = new RouteTester(routeCollection);

         Assert.DoesNotThrow(
            () =>
               {
                  tester.WithIncomingRequest("/Home/About/5").ShouldMatchRoute("Home", "About",
                                                                               new {id = 5, slug = "default-slug"});
               },
            "Requested URL should have matched a route.");
      }

      [Test]
      public void ShouldMatchRoute_WithRequestOverridingDefault_ShouldWorkFine()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "{controller}/{action}/{id}",
            new {id = "defaultId"}
            );

         var tester = new RouteTester(routeCollection);

         Assert.DoesNotThrow(
            () => { tester.WithIncomingRequest("/Home/About/5").ShouldMatchRoute("Home", "About", new {id = 5}); },
            "Requested URL should have matched a route.");
      }

      [Test]
      public void ShouldMatchRoute_WithRequestParameter_ShouldWorkFine()
      {
         var routeCollection = new RouteCollection();
         routeCollection.MapRoute(
            "TestRoute",
            "{controller}/{action}/{id}"
            );

         var tester = new RouteTester(routeCollection);

         Assert.DoesNotThrow(
            () => { tester.WithIncomingRequest("/Home/About/5").ShouldMatchRoute("Home", "About", new {id = 5}); },
            "Requested URL should have matched a route.");
      }
   }
}